/*
 * Controlador.cpp
 *
 */

#include "Controlador.h"

/**
 * Constructor del controlador.
 */
Controlador::Controlador( )
	: pidAscensores ( vector<pid_t>(CANTIDAD_DE_ASCENSORES) ),
	  colaAscensor ( Cola<Llamada>() ),
	  colaPisos ( Cola<Llamada>() ){
}

/**
 * Metodo que inicializa los mecanismos de concurrencia que usa el
 * controlador.
 * Crea las colas de mensajes para comunicarse con el ascensor y los
 * pisos y registra el handler de seniales.
 */
int Controlador::inicializar(){
	int retorno = -1;

	if (this->colaAscensor.crear( (char*) ARCHIVO_COLA_MSG_CA, ID_COLA_MSG ) != MSG_OK) {
		Log::getInstance()->loguear("Controlador: Error al crear la cola de mensages (Controlador - Ascensor).");
	}
	else if (this->colaPisos.crear( (char*) ARCHIVO_COLA_MSG_PC, ID_COLA_MSG ) != MSG_OK) {
		Log::getInstance()->loguear("Controlador: Error al crear la cola de mensages (Controlador - Pisos).");
	}
	else {
		SignalHandler::getInstance()->registrarHandler( SIGABRT, this );
		SignalHandler::getInstance()->registrarHandler( SIGUSR2, this );
		this->bloquearSeniales();
		Log::getInstance()->loguear("Controlador: creado.");
		retorno = 0;
	}
	return retorno;
}

/**
 * Metodo que setea que las seniales SIGINT y SIGTERM se bloquean.
 * El objetivo es que el controlador no salga del loop de la cola para
 * manejar estas seniales.
 * Las seniales SIGINT y SIGTERM las agarra el terminador para finalizar
 * la ejecucion correctamente.
 */
void Controlador::bloquearSeniales(){
	sigset_t set;
	sigemptyset( &set );
	sigaddset( &set, SIGINT );
	sigaddset( &set, SIGTERM );
	sigprocmask ( SIG_BLOCK, &set, NULL );
}

/**
 * Destructor del controlador.
 * Elimina el handler de seniales.
 */
Controlador::~Controlador() {
	SignalHandler::getInstance()->removerHandler( SIGABRT );
	SignalHandler::getInstance()->removerHandler( SIGUSR2 );
	SignalHandler::destruir();

	Log::getInstance()->loguear("Controlador: destruido.");
}

/**
 * Handler de seniales:
 * SIGABRT: Senial que envia el terminador en caso de que haya errores.
 * 		Se finaliza la ejecucion inmediatamente liberando los recursos.
 * SIGUSR2: Senial que envia el terminador para finalizar la ejecucion.
  */
int Controlador::handleSignal( int signum ){
	if ( signum == SIGABRT ){
		Log::getInstance()->loguear("Controlador: me llego SIGABRT. Finalizo ante errores.");
		this->~Controlador();
		exit( -1 );
	}
	else if ( signum == SIGUSR2 ){
		Log::getInstance()->loguear("Controlador: me llego SIGUSR2, no recibo mas llamadas.");
	}
	return 0;
}

/*
 * Se agrega el identificador del ascensor indicado al vevtor de identificadores.
 */
void Controlador::agregarPidAscensor(pid_t pidAscensor, int numeroAscensor) {
	this->pidAscensores[numeroAscensor] = pidAscensor;
}

/**
 * Metodo que se ejecuta de forma indefinida hasta que se reciba por parte del
 * Terminador una senial.
 * Permanece bloqueado leyendo la cola de mensajes, esperando a que los pisos
 * envien llamadas al ascensor.
 */
void Controlador::recibirLlamadas(){
	Llamada llamada;
	while ( this->colaPisos.leer( 0, &llamada ) > 0 ) {
		Log::getInstance()->loguear("Controlador: recibi llamada del piso " + intToString( llamada.piso ) + " desde el pipe.");
		int numeroAscensor = this->preguntarAscensor(llamada.piso);
		llamada.mtype = numeroAscensor+1;
		this->avisarAscensor(numeroAscensor, llamada );
	}
	Log::getInstance()->loguear("Controlador: no se reciben mas llamadas al ascensor.");
}

/**
 * Metodo que escribe una llamada en la cola de mensaje, y le envia
 * una senial al ascensor correspondiente avisandole para que lea
 * dicha llamada.
 */
void Controlador::avisarAscensor(int numeroAscensor, Llamada llamada){
	this->colaAscensor.escribir(llamada);
	kill( pidAscensores[numeroAscensor], SIGUSR1 );
}

/**
 * Metodo utilizado para averiguar el piso y el estado (subiendo,
 * bajando o detenido) de todos los ascensores, y en base a dicha
 * informacion, decidir a que ascensor se debe enviar la llamada.
 */
int Controlador::preguntarAscensor(int pisoPedido) {
	for (int nroAscensor = 0; nroAscensor < CANTIDAD_DE_ASCENSORES; ++nroAscensor) {
		kill( pidAscensores[nroAscensor], SIGCHLD );
	}
	Llamada estadoAscensor[CANTIDAD_DE_ASCENSORES];
	int metricaAscensor[CANTIDAD_DE_ASCENSORES];

	/* En este caso la llamada que se lee de la cola representa el estado del ascensor
	 * y no una llamda diferente. Se utiliza la misma estructura, puesto que la informacion
	 * requeriada en ambos casos es la misma. */
	Llamada llamada;
	for (int numeroAscensor = 0; numeroAscensor < CANTIDAD_DE_ASCENSORES; ++numeroAscensor) {
		this->colaAscensor.leer(numeroAscensor+CANTIDAD_DE_ASCENSORES, &llamada);
		estadoAscensor[numeroAscensor] = llamada;
		metricaAscensor[numeroAscensor] = this->obtenerMetricaAscensor(pisoPedido, llamada);
	}

	int mejorMetrica = 4;
	int numero;
	for (int numeroAscensor = 0; numeroAscensor < CANTIDAD_DE_ASCENSORES; ++numeroAscensor) {
		if (metricaAscensor[numeroAscensor] < mejorMetrica) {
			mejorMetrica = metricaAscensor[numeroAscensor];
			numero = numeroAscensor;
		}
		else if (metricaAscensor[numeroAscensor] == mejorMetrica) {
			int dist1 = pisoPedido - estadoAscensor[numeroAscensor].piso;
			int dist2 = pisoPedido - estadoAscensor[numero].piso;
			if (abs(dist1) < abs(dist2)) numero = numeroAscensor;
		}
	}
	return numero;
}

/*
 * Metodo que retorna segun el piso que requiera un ascensor, y el estado del
 * ascensor dado por "llamada" una metrica la cual indica:
 * 0 - El ascensor se encuentra detenido en el mismo piso donde se lo requiere
 * 1 - El ascensor se encuentra acercandose al piso donde se lo requiere
 * 2 - El ascensor se encuentra detenido en un piso distinto al piso donde se lo requiere
 * 3 - El ascensor se encuentra alejandose deñ piso donde se lo requiere
 */
int Controlador::obtenerMetricaAscensor(int pisoPedido, Llamada llamada) {

	if (llamada.tipoLlamada == LLAMADA_INTERNA) {
		//El ascensor se encuentra detenido
		if (llamada.piso == pisoPedido) return 0;
		else return 2;
	}
	else if (llamada.tipoLlamada == LLAMADA_SUBIR) {
		if (llamada.piso < pisoPedido) return 1;
		else return 3;
	}
	else {
		//El ascensor se encuentra bajando
		if (llamada.piso > pisoPedido) return 1;
		else return 3;
	}
}
